package com.learn.eventbus.compiler;

import com.google.auto.service.AutoService;
import com.learn.eventbus.annotation.BindView;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * @Retention注解
 * source：注解只保留在源文件，当Java文件编译成class文件的时候，注解被遗弃；被编译器忽略
 * class：注解被保留到class文件，但jvm加载class文件时候被遗弃，这是默认的生命周期
 * runtime：注解不仅被保存到class文件中，jvm加载class文件之后，仍然存在
 * 这3个生命周期分别对应于：Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
 *
 * 明确生命周期长度 SOURCE < CLASS < RUNTIME ，所以前者能作用的地方后者一定也能作用。
 * 一般如果需要在运行时去动态获取注解信息，那只能用 RUNTIME 注解；
 * 如果要在编译时进行一些预处理操作，比如生成一些辅助代码（如 ButterKnife），就用 CLASS注解；
 * 如果只是做一些检查性的操作，比如 @Override 和 @SuppressWarnings，则可选用 SOURCE 注解。
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationCompiler extends AbstractProcessor {

    Filer filer;
    Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager(); // 打印对象
        filer = processingEnv.getFiler(); // 文件操作对象
    }

    /**
     * 获取注解类型
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    /**
     * 声明支持的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    /**
     * 该方法能够得到注解标注的内容
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        /**
         * 得到BindView注解的Element节点
         * 类节点：TypeElement
         * 方法节点：ExecutableElement
         * 成员变量节点：VariableElement
         */
        Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
        // 存储每个Activity中的所有注解属性
        HashMap<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : bindViewElements) {
            VariableElement e = (VariableElement) element;
            // 获取上一个节点，也就是类名
            TypeElement typeElement = (TypeElement) e.getEnclosingElement();
            String activityName = typeElement.getSimpleName().toString();
            List<VariableElement> variableElementList = map.get(activityName);
            if (variableElementList == null) {
                variableElementList = new ArrayList<>();
                map.put(activityName, variableElementList);
            }
            // 缓存所有的属性注解节点
            variableElementList.add(e);
        }
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                List<VariableElement> variableElements = map.get(activityName);
                String packageName = getPackageName(variableElements.get(0));
                messager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
                String newClass = activityName + "$$ViewBind";
                try {
                    JavaFileObject javaFile = filer.createSourceFile(packageName + "." + newClass);// 生成源码文件
                    writer = javaFile.openWriter();
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("package " + packageName + ";\n");
                    buffer.append("import android.view.View;\n");
                    buffer.append("public class " + newClass + "{\n");
                    buffer.append("public " + newClass + "(final " + packageName + "." + activityName + " target){\n");
                    for (VariableElement element : variableElements) {
                        String fieldName = element.getSimpleName().toString();
                        int resId = element.getAnnotation(BindView.class).value();
                        TypeMirror typeMirror = element.asType();
                        buffer.append("target." + fieldName + "=target.findViewById(" + resId + ");\n");
                    }
                    buffer.append("}\n}");
                    writer.write(buffer.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (writer != null) {
                        writer.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
        return false;
    }

    /**
     * 通过VariableElement获取包名
     */
    private String getPackageName(VariableElement element) {
        TypeElement activityElement = (TypeElement) element.getEnclosingElement();
        // 通过类名获取包名
        PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(activityElement);
        Name packageName = packageElement.getQualifiedName();
        return packageName.toString();
    }
}
